package com.tn.utils;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.tn.controller.alarm.common.model.InputVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpRequestSenderUtil {
    private static final String DEFAULT_ENCODING = "UTF-8"; // 默认编码方式
    private static final int CONNECT_POOL_TIMEOUT = 5000; // 从连接池获取可用连接的时间
    private static final int CONNECT_TIMEOUT = 10000; // 连接超时时间
    private static final int SOCKET_TIMEOUT = 10000; // 响应超时时间
    private static final int MAX_CONNECT_NUM = 100; // 最大连接数
    private static final int MAX_PER_ROUTE = 10; // 每个主机的并发
    private static final int MAX_ROUTE = 10; // 目标主机的最大连接数
    private static volatile CloseableHttpClient httpClient = null;
    private static PoolingHttpClientConnectionManager poolingManager = null;
    private static ScheduledExecutorService monitorExecutor;

    /**
     * 获取连接客户端
     *
     * @param url 请求地址
     * @return 客户端实例
     */
    public static CloseableHttpClient getHttpClient(final String url) {
        String hostName = url.split("/")[2];
        int port = 80;
        if (hostName.contains(":")) {
            final String[] arr = hostName.split(":");
            hostName = arr[0];
            port = Integer.parseInt(arr[1]);
        }
        if (null == HttpRequestSenderUtil.httpClient) {
            synchronized (HttpRequestSenderUtil.class) {
                if (null == HttpRequestSenderUtil.httpClient) {
                    // 开启监控线程，对异常和空闲线程进行关闭
                    HttpRequestSenderUtil.monitorExecutor = Executors.newScheduledThreadPool(1);
                    HttpRequestSenderUtil.monitorExecutor.scheduleAtFixedRate(
                            new TimerTask() {
                                @Override
                                public void run() {
                                    HttpRequestSenderUtil.poolingManager.closeExpiredConnections();
                                    HttpRequestSenderUtil.poolingManager.closeIdleConnections(CONNECT_POOL_TIMEOUT,
                                            TimeUnit.MILLISECONDS);
                                }
                            }, CONNECT_POOL_TIMEOUT, CONNECT_POOL_TIMEOUT, TimeUnit.MILLISECONDS
                    );
                    HttpRequestSenderUtil.httpClient = HttpRequestSenderUtil.createHttpClient(MAX_CONNECT_NUM,
                            MAX_PER_ROUTE,
                            MAX_ROUTE, hostName, port);
                }
            }
        }
        return HttpRequestSenderUtil.httpClient;
    }

    /**
     * 创建请求客户端
     *
     * @param maxTotal    最大连接数
     * @param maxPerRoute 每个主机的并发数
     * @param maxRoute    目标主机的最大连接数
     * @param hostName    主机地址
     * @param port        端口
     * @return 客户端实例
     */
    private static CloseableHttpClient createHttpClient(final int maxTotal, final int maxPerRoute, final int maxRoute
            , final String hostName, final int port) {
        final ConnectionSocketFactory plainsFactory = PlainConnectionSocketFactory.getSocketFactory();
        final LayeredConnectionSocketFactory sslsFactory = SSLConnectionSocketFactory.getSocketFactory();
        final Registry<ConnectionSocketFactory> registry =
                RegistryBuilder.<ConnectionSocketFactory>create().register("http", plainsFactory).register("https",
                        sslsFactory).build();
        HttpRequestSenderUtil.poolingManager = new PoolingHttpClientConnectionManager(registry);
        HttpRequestSenderUtil.poolingManager.setMaxTotal(maxTotal);
        HttpRequestSenderUtil.poolingManager.setDefaultMaxPerRoute(maxPerRoute);
        final HttpHost httpHost = new HttpHost(hostName, port);
        HttpRequestSenderUtil.poolingManager.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);
        // 请求重试处理
        final HttpRequestRetryHandler httpRequestRetryHandler = (e, i, httpContext) -> {
            if (i >= 2) {
                log.info("重试2次，放弃");
                return false;
            }
            if (e instanceof NoHttpResponseException) {
                log.info("服务器丢掉连接，重试");
                return true;
            }
            if (e instanceof SSLHandshakeException) {
                log.info("不要重试SSL握手异常，放弃");
                return false;
            }
            if (e instanceof InterruptedIOException) {
                log.info("中断，放弃");
                return false;
            }
            if (e instanceof UnknownHostException) {
                log.info("目标服务器不可达，放弃");
                return false;
            }
            if (e instanceof SSLException) {
                log.info("SSL握手异常，放弃");
                return false;
            }
            final HttpClientContext clientContext = HttpClientContext.adapt(httpContext);
            final HttpRequest request = clientContext.getRequest();
            if (!(request instanceof HttpEntityEnclosingRequest)) {
                return true;
            }
            return false;
        };
        return HttpClients.custom().setConnectionManager(HttpRequestSenderUtil.poolingManager)
                .setRetryHandler(httpRequestRetryHandler)
                .build();
    }

    /**
     * 发送请求
     *
     * @param httpBase 请求对象
     * @param url      路径
     * @return 结果
     */
    private static String httpMethod(final HttpRequestBase httpBase, final String url) {
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        try {
            final RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(HttpRequestSenderUtil.CONNECT_POOL_TIMEOUT)
                    .setConnectTimeout(HttpRequestSenderUtil.CONNECT_TIMEOUT)
                    .setSocketTimeout(HttpRequestSenderUtil.SOCKET_TIMEOUT)
                    .build();
            httpBase.setConfig(requestConfig);
            response = HttpRequestSenderUtil.getHttpClient(url).execute(httpBase, HttpClientContext.create());
            entity = response.getEntity();
            return EntityUtils.toString(entity, HttpRequestSenderUtil.DEFAULT_ENCODING);
        } catch (final Exception e) {
            e.printStackTrace();
        } finally {
            try {
                EntityUtils.consume(entity);
                if (response != null) {
                    response.close();
                }
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * POST请求
     *
     * @param url 地址
     * @return 结果
     */
    public static String post(String url) {
        return HttpRequestSenderUtil.post(url, null, null);
    }

    /**
     * POST请求
     *
     * @param url    地址
     * @param params 请求体
     * @return 结果
     */
    public static String post(String url, InputVO params) {
        return HttpRequestSenderUtil.post(url, null, params);
    }

    /**
     * 发送post请求
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  请求参数
     * @return 请求结果
     */
    public static String post(String url, Map<String, String> headers, InputVO params) {
        return postString(url, headers, JSON.toJSONString(params));
    }

    /**
     * 发送post请求
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  请求参数
     * @return 请求结果
     */
    public static String postString(String url, Map<String, String> headers, String params) {
        log.info("请求地址：" + url);
        log.info("请求参数：" + params);
        HttpPost httpPost = new HttpPost(url);
        if (null == headers) {
            headers = new HashMap<>();
            headers.put("Content-Type", "application/json;charset=UTF-8");
        }
        packageHeader(headers, httpPost);
        if (StringUtils.isNotBlank(params)) {
            StringEntity entity = new StringEntity(params, DEFAULT_ENCODING);
            httpPost.setEntity(entity);
        }
        String response = HttpRequestSenderUtil.httpMethod(httpPost, url);
        String ts = "";
        if(StrUtil.isNotBlank(params) && params.length() > 15) {
            ts = params.substring(params.length()-13-1, params.length() -1);
        } else {
            ts = params;
        }
        log.info("参数{} 的响应信息：{}", ts, response);
        return response;
    }

    /**
     * 发送get请求
     *
     * @param url 请求地址
     * @return 请求结果
     */
    public static String get(String url) {
        HttpGet httpGet = new HttpGet(url);
        return HttpRequestSenderUtil.httpMethod(httpGet, url);
    }

    /**
     * 封装请求头
     *
     * @param headers    请求头参数
     * @param httpMethod 请求对象
     */
    private static void packageHeader(Map<String, String> headers, HttpRequestBase httpMethod) {
        if (null != headers) {
            Iterator<Map.Entry<String, String>> iterator = headers.entrySet().iterator();
            Map.Entry<String, String> entry;
            while (iterator.hasNext()) {
                entry = iterator.next();
                httpMethod.setHeader(entry.getKey(), entry.getValue() == null ? "" : entry.getValue());
            }
        }
    }
}
